package com.wj.init.service.impl;

import com.wj.init.config.constant.SqlKey;
import com.wj.init.entity.BaseEntity;
import com.wj.init.repository.BaseRepository;
import com.wj.init.service.BaseService;
import com.wj.init.utils.DateUtils;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author 吴健
 * @date 2019/9/2 15:34
 * @desc 基础service
 */
abstract class BaseServiceImpl<T extends BaseEntity> implements BaseService<T> {

    /**
     * 获取repo
     *
     * @return
     */
    abstract BaseRepository<T> getRepo();

    private static final String LIKE_SIGN = "%";

    private static final String PARAM_SEPARATOR = ",";

    private static final String ID_FIELD_NAME = "id";
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean save(T model) {
        return getRepo().save(model) != null;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean save(List<T> list) {
        return getRepo().saveAll(list).size() == list.size();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteById(String id) {
        getRepo().deleteById(id);
    }

    @Override
    public List<T> getList(Pageable page, T model) {
        List<Field> fields = new ArrayList<>(Arrays.asList(model.getClass().getDeclaredFields()));
        //由于所有数据实体继承BaseEntity,getDeclaredFields()无法获取父类字段
        fields.addAll(Arrays.asList(model.getClass().getSuperclass().getDeclaredFields()));
        ExampleMatcher matcher = ExampleMatcher.matching();
        for (Field field : fields) {
            //String类型字符使用like匹配
            if (field.getType().equals(String.class) && !ID_FIELD_NAME.equalsIgnoreCase(field.getName())) {
                matcher = matcher.withMatcher(field.getName(), ExampleMatcher.GenericPropertyMatchers.contains());
            }
        }
        Example<T> example = Example.of(model, matcher);
        return getRepo().findAll(example, page).getContent();
    }

    @Override
    public List<T> getList(Pageable pageable, Map<String, String[]> params) throws Exception {
        AtomicBoolean error = new AtomicBoolean(false);
        Page<T> page = getRepo().findAll(((root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            params.forEach((k, v) -> {
                try {
                    Predicate predicate = getPredicate(criteriaBuilder, root, k, v[0]);
                    if (predicate != null) {
                        predicates.add(predicate);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    error.set(true);
                }
            });
            criteriaQuery.where(criteriaBuilder.and(predicates.toArray(new Predicate[0])));
            return criteriaQuery.getRestriction();
        }), pageable);
        if (error.get()) {
            throw new Exception("日期解析错误");
        }
        return page.getContent();
    }

    @Override
    public List<T> getList(Pageable pageable, Object model) throws Exception {

        return null;
    }

    /**
     * 根据参数获取限制predicate
     *
     * @param criteriaBuilder
     * @param root
     * @param key
     * @param value
     * @return
     * @throws Exception
     */
    private Predicate getPredicate(CriteriaBuilder criteriaBuilder, Root<T> root, String key, Object value) throws Exception {
        String[] name = key.split("_");
        //获取字段类型
        Path path = root.get(name[0]);
        Expression expression = path.type();
        Class classT = expression.getJavaType();
        //传入参数处理
        //时间类型时将时间转换为java.util.Date类型
        if (!classT.isAssignableFrom(String.class) && !classT.isAssignableFrom(Character.class)) {
            if (classT.isAssignableFrom(Date.class)) {
                if (name.length > 1 && SqlKey.IN.getValue().equalsIgnoreCase(name[1])) {
                    //多个参数时先格式化, 然后使用List装载
                    List<Date> dateValues = new ArrayList<>();
                    for (String s : value.toString().split(PARAM_SEPARATOR)) {
                        dateValues.add(DateUtils.getDateFromString(s, DateUtils.PATTERN_4));
                    }
                    value = dateValues;
                } else {
                    value = DateUtils.getDateFromString(value.toString(), DateUtils.PATTERN_4);
                }
            } else if (classT.isAssignableFrom(Number.class)) {
                //数字类型统一使用使用BigDecimal类型查询
                if (name.length > 1 && SqlKey.IN.getValue().equalsIgnoreCase(name[1])) {
                    //多个参数时先格式化, 然后使用List装载
                    List<BigDecimal> dateValues = new ArrayList<>();
                    for (String s : value.toString().split(PARAM_SEPARATOR)) {
                        dateValues.add(new BigDecimal(s));
                    }
                    value = dateValues;
                } else {
                    value = new BigDecimal(value.toString());
                }
            }
        }
        Predicate predicate = null;
        //根据参数名判断查询类型
        if (name.length == 1) {
            //无具体条件时默认使用等额查询
            predicate = criteriaBuilder.equal(path, value);
        } else {
            if (SqlKey.GREATER_THEN.getValue().equalsIgnoreCase(name[1])) {
                if (classT.isAssignableFrom(Date.class)) {
                    predicate = criteriaBuilder.greaterThan(path, (Date) value);
                } else if (classT.isAssignableFrom(Number.class)) {
                    predicate = criteriaBuilder.greaterThan(path, (BigDecimal) value);
                } else {
                    predicate = criteriaBuilder.greaterThan(path, value.toString());
                }
            } else if (SqlKey.GREATER_THEN_EQUAL.getValue().equalsIgnoreCase(name[1])) {
                if (classT.isAssignableFrom(Date.class)) {
                    predicate = criteriaBuilder.greaterThanOrEqualTo(path, (Date) value);
                } else if (classT.isAssignableFrom(Number.class)) {
                    predicate = criteriaBuilder.greaterThanOrEqualTo(path, (BigDecimal) value);
                } else {
                    predicate = criteriaBuilder.greaterThanOrEqualTo(path, value.toString());
                }
            } else if (SqlKey.LESS_THEN.getValue().equalsIgnoreCase(name[1])) {
                if (classT.isAssignableFrom(Date.class)) {
                    predicate = criteriaBuilder.lessThan(path, (Date) value);
                } else if (classT.isAssignableFrom(Number.class)) {
                    predicate = criteriaBuilder.lessThan(path, (BigDecimal) value);
                } else {
                    predicate = criteriaBuilder.lessThan(path, value.toString());
                }
            } else if (SqlKey.LESS_THEN_EQUAL.getValue().equalsIgnoreCase(name[1])) {
                if (classT.isAssignableFrom(Date.class)) {
                    predicate = criteriaBuilder.lessThanOrEqualTo(path, (Date) value);
                } else if (classT.isAssignableFrom(Number.class)) {
                    predicate = criteriaBuilder.lessThanOrEqualTo(path, (BigDecimal) value);
                } else {
                    predicate = criteriaBuilder.lessThanOrEqualTo(path, value.toString());
                }
            } else if (SqlKey.EQUAL.getValue().equalsIgnoreCase(name[1])) {
                predicate = criteriaBuilder.equal(path, value);
            } else if (SqlKey.CONTAINS.getValue().equalsIgnoreCase(name[1])) {
                predicate = criteriaBuilder.like(path, LIKE_SIGN + value + LIKE_SIGN);
            } else if (SqlKey.START_WITH.getValue().equalsIgnoreCase(name[1])) {
                predicate = criteriaBuilder.like(path, value + LIKE_SIGN);
            } else if (SqlKey.END_WITH.getValue().equalsIgnoreCase(name[1])) {
                predicate = criteriaBuilder.like(path, LIKE_SIGN + value);
            } else if (SqlKey.IS_NULL.getValue().equalsIgnoreCase(name[1])) {
                predicate = criteriaBuilder.isNull(path);
            } else if (SqlKey.NOT_NULL.getValue().equalsIgnoreCase(name[1])) {
                predicate = criteriaBuilder.isNotNull(path);
            } else if (SqlKey.IN.getValue().equalsIgnoreCase(name[1])) {
                predicate = criteriaBuilder.in(path).value(value);
            }
        }
        return predicate;
    }
}
